(ns lastfm-music-recommendation.search.nnet
  (:require [lastfm-music-recommendation.db.datomic :as datomic])
  (:require [clojure.string :as s]))

(defn dtanh
  [y]
  (- 1 (* y y)))

(defn feed-forward
  [words songs]
  (let [hiddens (datomic/get-all-hidden words songs)
        wi (reduce #(conj %1
                          (reduce (fn [r h]
                                    (conj r (datomic/get-strength-wordhidden %2 h)))
                                  [] hiddens))
                   [] words)
        wo (reduce #(conj %1
                          (reduce (fn [r s]
                                    (conj r (datomic/get-strength-hiddensong %2 s)))
                                  [] songs))
                   [] hiddens)
        ah (reduce (fn [r j] (assoc r j (Math/tanh 
                                          (reduce (fn [sum i] (+ sum ((wi i) j)))
                                                  0 (range 0 (count words)))))) 
                   [] (range 0 (count hiddens)))
        ao (reduce (fn [r k] (assoc r k (Math/tanh 
                                  (reduce (fn [sum j] (+ sum (* (ah j) ((wo j) k))))
                                          0 (range 0 (count hiddens)))))) 
                   [] (range 0 (count songs)))]
      [hiddens ah ao wi wo]))

(defn get-result
  [query songs]
  (let [words (s/split query #" ")] 
    (datomic/generate-hidden-node words songs)
    ((feed-forward words songs) 2)))

(defn back-propagate
  [targets words songs]
  (let [[hiddens ah ao wi wo] (feed-forward words songs)
        output-deltas (reduce (fn [r k] (conj r (* (dtanh (ao k)) 
                                                   (- (targets k) (ao k))))) 
                              [] (range 0 (count songs)))
        hidden-deltas (reduce (fn [r j] (conj r (* (dtanh (ah j))
                                                   (reduce (fn [err k] (+ err
                                                                          (* (output-deltas k)
                                                                             ((wo j) k)))) 
                                                           0 (range 0 (count songs))))))
                              [] (range 0 (count hiddens)))
        new-wo (reduce (fn [res j] (reduce (fn [r k] (assoc-in r [j k] (+ ((r j) k)
                                                                          (* 0.5 (* (output-deltas k)
                                                                                    (ah j)))))) 
                                         res (range 0 (count songs))))
                       wo (range 0 (count hiddens)))
        new-wi (reduce (fn [res i] (reduce (fn [r j] (assoc-in r [i j] (+ ((r i) j)
                                                                          (* 0.5 (* (hidden-deltas j)
                                                                                    (ao i))))))
                                           res (range 0 (count hiddens))))
                       wi (range 0 (count words)))]
    [hiddens new-wo new-wi]))

(defn update-db
  [words songs hiddens wo wi]
  (dorun (for [i (range 0 (count words))
               j (range 0 (count hiddens))]
           (datomic/set-strength-wordhidden (words i)
                                            (hiddens j)
                                            ((wi i) j)))) 
  (dorun (for [j (range 0 (count hiddens))
               k (range 0 (count songs))]
           (datomic/set-strength-hiddensong (hiddens j)
                                            (songs k)
                                            ((wo j) k)))))

(defn train-query
  [query songs selected]
  (let [words (s/split query #" ")
        targets (assoc (vec (repeat (count songs) 0.0)) selected 1)
        hn (datomic/generate-hidden-node words songs)
        [hiddens wo wi] (back-propagate targets words songs)]
   (update-db words songs hiddens wo wi)))



